import UIKit

// 游戏回调
protocol GameCallback: NSObjectProtocol {
    func onGameOver(_ isWon: Bool)  // 游戏结束事件
}

// 游戏
class Game: NSObject {
    // 状态
    public enum Status {
        case END        // 已结束
        case PLAYING    // 游戏中
    }

    private let mCallback: GameCallback // 回调
    private let mDrawer: CGContext      // 绘图者
    private let mFont: UIFont!          // 字体
    private let mGameBoard: UIView      // 游戏区域
    private var mNumbers: [[Int]]!      // 数字矩阵
    private var mStatus: Status         // 状态

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public init(_ callback: GameCallback, drawer: CGContext, gameBoard: UIView) {
        // 初始化成员
        mCallback = callback
        mDrawer = drawer
        mFont = UIFont.systemFont(ofSize: Const.GRID_SIZE * Const.SCALE_GAME_BOARD_TEXT_SIZE)
        mGameBoard = gameBoard
        mStatus = Status.END

        super.init()

        // 重绘游戏区域
        self.drawGameBoard()
    }
    
    /**
     * 移动之后要做的事。
     * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
     */
    private func afterMove(_ changed: Bool) {
        // 重绘游戏区域
        self.drawGameBoard()
    
        // 画所有数字
        self.drawAllNumbers()
    
        // 如果胜利或失败，则游戏结束
        if self.isWon() {
            self.gameOver(true)
            return
        }
        if self.isLost() {
            self.gameOver(false)
            return
        }

        // 创建下一个数
        if changed {
            self.createRandomNumber()
        }
    }

    /**
     * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
     */
    private func createRandomNumber() {
        var row = Int(arc4random_uniform(UInt32(Const.GAME_BOARD_GRID_COUNT_V)))
        var line = Int(arc4random_uniform(UInt32(Const.GAME_BOARD_GRID_COUNT_H)))

        while mNumbers[row][line] != 0 {
            row = Int(arc4random_uniform(UInt32(Const.GAME_BOARD_GRID_COUNT_V)))
            line = Int(arc4random_uniform(UInt32(Const.GAME_BOARD_GRID_COUNT_H)))
        }

        mNumbers[row][line] = arc4random_uniform(8) <= 3 ? 2: 4
        self.drawOneNumber(line, row: row, number: mNumbers[row][line])
        mGameBoard.setNeedsDisplay()
    }
    
    /**
     * 画所有数字。
     */
    private func drawAllNumbers() {
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                if mNumbers[i][j] != 0 {
                    self.drawOneNumber(j, row: i, number: mNumbers[i][j])
                }
            }
        }
        mGameBoard.setNeedsDisplay()
    }

    /**
     * 画游戏区域。
     */
    private func drawGameBoard() {
        // 画边框
        var width = mGameBoard.frame.width
        var height = mGameBoard.frame.height
        mDrawer.setStrokeColor(Const.GAME_BOARD_BORDER_COLOR)
        mDrawer.stroke(CGRect(x: 0, y: 0, width: width - 1, height: height - 1))
        
        // 画背景
        mDrawer.setFillColor(Const.GAME_BOARD_COLOR)
        mDrawer.fill(CGRect(x: 1, y: 1, width: width - 2.5, height: height - 2.5))

        // 画竖线
        width = 0.5
        height = Const.GRID_SIZE * CGFloat(Const.GAME_BOARD_GRID_COUNT_V)
        mDrawer.setFillColor(Const.GAME_BOARD_BORDER_COLOR)
        for i in 1 ..< Const.GAME_BOARD_GRID_COUNT_V {
            mDrawer.fill(CGRect(x: Const.GRID_SIZE * CGFloat(i), y: 0, width: width, height: height))
        }

        // 画横线
        width = Const.GRID_SIZE * CGFloat(Const.GAME_BOARD_GRID_COUNT_H)
        height = 0.5
        for i in 1 ..< Const.GAME_BOARD_GRID_COUNT_H {
            mDrawer.fill(CGRect(x: 0, y: Const.GRID_SIZE * CGFloat(i), width: width, height: height))
        }

        // 通知游戏区域重绘
        mGameBoard.setNeedsDisplay()
    }

    /**
     * 画单个数字。
     * @param line      列
     * @param row       行
     * @param number    要画的数字
     */
    private func drawOneNumber(_ line: Int, row: Int, number: Int) {
        // 设置属性
        let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        textStyle.alignment = NSTextAlignment.center
        let textFontAttributes: [NSAttributedString.Key: Any] = [
            NSAttributedString.Key.font: mFont!,
            NSAttributedString.Key.foregroundColor: Const.GAME_BOARD_FONT_COLOR,
            NSAttributedString.Key.paragraphStyle: textStyle
        ]

        // 计算位置
        let x = CGFloat(line) * Const.GRID_SIZE
        let y = CGFloat(row) * Const.GRID_SIZE + (Const.GRID_SIZE - mFont.pointSize) / 2 - 1

        // 画数字
        UIGraphicsPushContext(mDrawer)
        NSString(format: "%d", number).draw(in: CGRect(x: x, y: y, width: Const.GRID_SIZE, height: Const.GRID_SIZE), withAttributes: textFontAttributes)
        UIGraphicsPopContext()
    }

    /**
     * 游戏结束。
     * @param isWon 胜利则为 true，否则为 false
     */
    private func gameOver(_ isWon: Bool) {
        // 将状态设置为已结束
        mStatus = .END

        // 发送通知
        mCallback.onGameOver(isWon)
    }
    
    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    private func isLost() -> Bool {
        // 横向检查所有数字
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H - 1 {
                if mNumbers[i][j] == 0 || mNumbers[i][j] == mNumbers[i][j + 1] {
                    return false
                }
            }
        }

        // 纵向检查所有数字
        for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
            for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V - 1 {
                if mNumbers[i][j] == mNumbers[i + 1][j] {
                    return false
                }
            }
        }

        // 判断最右边一列是否有零
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            if mNumbers[i][3] == 0 {
                return false
            }
        }

        return true
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private func isWon() -> Bool {
        // 检查所有数字
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
                if mNumbers[i][j] == Const.WIN_NUMBER {
                    return true
                }
            }
        }
        return false
    }

    /**
     * 下移。
     */
    private func moveDown() {
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
            // 每列从上到下遍历每一格，判断是否有相同数，有则相加
            for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
                // 如果该数为零或之前被加过，则什么也不做
                if mNumbers[i][j] == 0 || i == ignore {
                    continue
                }

                // 不为零则从该数下方逐个检查
                for k in i + 1 ..< Const.GAME_BOARD_GRID_COUNT_V {
                    // 遇到零跳过
                    if mNumbers[k][j] == 0 {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if mNumbers[k][j] != mNumbers[i][j] {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }
    
            // 所有数字向下移动
            for i in (0 ... Const.GAME_BOARD_GRID_COUNT_V - 2).reversed() {
                // 如果该数为零（没有数），则什么也不做
                if mNumbers[i][j] == 0 {
                    continue
                }

                // 不为零则向下移动
                for k in i + 1 ..< Const.GAME_BOARD_GRID_COUNT_V {
                    if mNumbers[k][j] == 0 {
                        mNumbers[k][j] = mNumbers[k - 1][j]
                        mNumbers[k - 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        self.afterMove(changed)
    }
    
    /**
     * 左移。
     */
    private func moveLeft() {
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动
    
        // 从上到下遍历每一行
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            // 每行从右到左遍历每一格，判断是否有相同数，有则相加
            for j in (1 ... Const.GAME_BOARD_GRID_COUNT_H - 1).reversed() {
                // 如果该数为零或之前被加过，则什么也不做
                if mNumbers[i][j] == 0 || j == ignore {
                    continue
                }

                // 不为零则从该数左面逐个检查
                for k in (0 ... j - 1).reversed() {
                    // 遇到零跳过
                    if mNumbers[i][k] == 0 {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if mNumbers[i][k] != mNumbers[i][j] {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }
    
            // 所有数字向左移动
            for j in 1 ..< Const.GAME_BOARD_GRID_COUNT_H {
                // 如果该数为零（没有数），则什么也不做
                if mNumbers[i][j] == 0 {
                    continue
                }

                // 不为零则向左移动
                for k in (0 ... j - 1).reversed() {
                    if mNumbers[i][k] == 0 {
                        mNumbers[i][k] = mNumbers[i][k + 1]
                        mNumbers[i][k + 1] = 0
                        changed = true
                    }
                }
            }
        }

        self.afterMove(changed)
    }
    
    /**
     * 右移。
     */
    private func moveRight() {
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动
    
        // 从上到下遍历每一行
        for i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            // 每行从左到右遍历每一格，判断是否有相同数，有则相加
            for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H - 1 {
                // 如果该数为零或之前被加过，则什么也不做
                if mNumbers[i][j] == 0 || j == ignore {
                    continue
                }

                // 不为零则从该数右面逐个检查
                for k in j + 1 ..< Const.GAME_BOARD_GRID_COUNT_H {
                    // 遇到零跳过
                    if mNumbers[i][k] == 0 {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if mNumbers[i][k] != mNumbers[i][j] {
                        break
                    }
 
                     // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }
    
            // 所有数字向右移动
            for j in (0 ... Const.GAME_BOARD_GRID_COUNT_H - 2).reversed() {
                // 如果该数为零（没有数），则什么也不做
                if mNumbers[i][j] == 0 {
                    continue
                }

                // 不为零则向右移动
                for k in j + 1 ..< Const.GAME_BOARD_GRID_COUNT_H {
                    if mNumbers[i][k] == 0 {
                        mNumbers[i][k] = mNumbers[i][k - 1]
                        mNumbers[i][k - 1] = 0
                        changed = true
                    }
                }
            }
        }

        self.afterMove(changed)
    }
    
    /**
     * 上移。
     */
    private func moveUp() {
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H {
            // 每列从下到上遍历每一格，判断是否有相同数，有则相加
            for i in (1 ... Const.GAME_BOARD_GRID_COUNT_V - 1).reversed() {
                // 如果该数为零或之前被加过，则什么也不做
                if mNumbers[i][j] == 0 || i == ignore {
                    continue
                }

                // 不为零则从该数上方逐个检查
                for k in (0 ... i - 1).reversed() {
                    // 遇到零跳过
                    if mNumbers[k][j] == 0 {
                        continue
                    }
                    
                    // 如果两数不相等，则结束本次循环
                    if mNumbers[k][j] != mNumbers[i][j] {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }
    
            // 所有数字向上移动
            for i in 1 ..< Const.GAME_BOARD_GRID_COUNT_V {
                // 如果该数为零（没有数），则什么也不做
                if mNumbers[i][j] == 0 {
                    continue
                }

                // 不为零则向上移动
                for k in (0 ... i - 1).reversed() {
                    if mNumbers[k][j] == 0 {
                        mNumbers[k][j] = mNumbers[k + 1][j]
                        mNumbers[k + 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        self.afterMove(changed)
    }
    
    /**
     * 滑动手势的响应方法。
     * @param direction 方向
     */
    public func onFling(_ direction: UISwipeGestureRecognizer.Direction) {
        if mStatus != .PLAYING {
            return
        }

        switch direction {
            case .down:
                self.moveDown()
            case .left:
                self.moveLeft()
            case .right:
                self.moveRight()
            case .up:
                self.moveUp()
            default:
                break
        }
    }
    
    /**
     * 游戏开始。
     */
    public func start() {
        // 重绘游戏区域
        self.drawGameBoard()

        // 数字矩阵清零
        mNumbers = [[Int]]()
        for _ in 0 ..< Const.GAME_BOARD_GRID_COUNT_V {
            mNumbers.append(Array(repeating: 0, count: Const.GAME_BOARD_GRID_COUNT_H))
        }
    
        // 创建随机数字
        self.createRandomNumber()
    
        // 将状态设置为游戏中
        mStatus = .PLAYING
    }
}
